/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ibmmodel3;

import ParTreeVisualizer.SentenceAlignement;
import ParTreeVisualizer.WordAlignement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author mwh
 */
public class GIZAAlignment {

    final TranslationTable trans;

    final DistortionTable dist;

    final FertilityTable fert;

    final ZeroFertility zeroFert;

    final List<Integer> s1;

    final List<Integer> s2;

    public Map<Integer, Set<Integer>> a;

    Set<Integer> alreadyAligned;

    final public List<String> ss1;

    final public List<String> ss2;

    final int l; // s1 length

    final int m; // s2 length

    final double p0log;

    final double p1log;

    double Pchooselog;

    double Pp0log;

    double Pp1log;

    double Psprodlog;

    double Ptprodlog;

    double res;


    public GIZAAlignment(List<String> ss1, List<String> ss2, TranslationTable trans, DistortionTable dist, FertilityTable fert, VocabularyTable vocab1, VocabularyTable vocab2, ZeroFertility zeroFert) {

        this.ss1 = ss1;
        this.ss2 = ss2;
        this.trans = trans;
        this.dist = dist;
        this.fert = fert;
        this.zeroFert = zeroFert;
        s1 = new ArrayList<Integer>();
        s2 = new ArrayList<Integer>();

        // NULL
        s1.add(-1);
        s2.add(-1);



        // Lookup IDs for words in vocabulary file
//        for (String s : ss1) {
        for (int i = 0; i < ss1.size(); i++) {
            String s = ss1.get(i);
            //  System.err.println(s);
            s1.add(vocab1.get(s));
            ss1.set(i, s + "/" + vocab1.get(s));
        }
        //for (String s : ss2) {
        for (int i = 0; i < ss2.size(); i++) {
            String s = ss2.get(i);

            //  System.err.println(s);
            s2.add(vocab2.get(s));
            ss2.set(i, s + "/" + vocab2.get(s));

        }

        // Empty alignment
        a = new HashMap<Integer, Set<Integer>>();

        // Add empty alignments for all words
        for (int i = 0; i < s1.size(); i++) {
            a.put(i, new HashSet<Integer>());
        }


        // Align all foreign words to NULL in sentence 1

        for (int i = 1; i < s2.size(); i++) {
            a.get(0).add(i);
        }


        // Which foreign words have already been aligned.
        alreadyAligned = new HashSet<Integer>();

        //System.err.println(s1);
        //System.err.println(s2);
        l = s1.size();
        m = s2.size();


        p0log = zeroFert.getLog10();
        p1log = Math.log10(1 - zeroFert.get());
        update();
    }


    private void addAlignmentNOUPD(int s1pos, int s2pos) {

//        // Is s2pos null aligned?
//        if (a.get(0).contains(s2pos)) {

        // Remove null-alignment
        a.get(0).remove(s2pos);
        //    }
        if (alreadyAligned.contains(s2pos)) {
            System.err.println("AddAlignNOUPD ERROR, Word " + s2pos + " is already aligned");
        }
        alreadyAligned.add(s2pos);
        a.get(s1pos).add(s2pos);

    }


    public double score() {
        return res;
    }


    public double apply(alignRes ar) {

        addAlignmentNOUPD(ar.source, ar.target);
        Pchooselog = ar.Pchooselog;
        Pp0log = ar.Pp0log;
        Pp1log = ar.Pp1log;
        Psprodlog = ar.Psprodlog;
        Ptprodlog = ar.Ptprodlog;
        res = ar.res;
        return res;
    }


    public alignRes tryAlign(int s1pos, int s2pos, boolean print) {


        double tryPchooselog;
        double tryPp0log;
        double tryPp1log;
        double tryPsprodlog;
        double tryPtprodlog;

        /**
         * TODO There is lots of optimizations possible here. Especially concerning the fact and choose
         */
        // First check if probabilities concerning spurious words change
        // Because if not, then Pchooselog, Pp0log and Pp1log does not change
        if (a.get(0).contains(s2pos)) {
            if (print) {
                System.err.println("phi0 change");
            }
            int phi0 = a.get(0).size() - 1;
            int mphi0 = m - phi0;


            double Pchoose = choose(mphi0, phi0);
            if (print) {
                System.err.println(mphi0 + " " + phi0 + "   " + choose(mphi0, phi0));
            }
            if (Pchoose <= 1) {
                if (print) {
                    System.err.println("Pchoose-error");
                }
                Pchoose = 1;
            }

            tryPchooselog = Math.log10(Pchoose);
            if (print) {
                System.err.println("Pchoose " + Pchoose);
                System.err.println("Pchooselog " + tryPchooselog);

            }

            int m2phi0 = (m - 2 * phi0);
            if (m2phi0 < 0) {
                m2phi0 = 1;
            }

            tryPp0log = m2phi0 * p0log;

            tryPp1log = phi0 * p1log;
        //System.err.println(phi0 + "  "  + tryPp1log);

        } else {
            tryPchooselog = Pchooselog;
            tryPp0log = Pp0log;
            tryPp1log = Pp1log;
        }


        int oldPhi = a.get(s1pos).size();
        int newPhi = oldPhi + 1;
        tryPsprodlog = Psprodlog - Math.log10(fact(oldPhi));
        tryPsprodlog = tryPsprodlog + Math.log10(fact(newPhi));
        double oldflog = fert.getLog10(s1.get(s1pos), oldPhi);
        double newflog = fert.getLog10(s1.get(s1pos), newPhi);
        tryPsprodlog = tryPsprodlog - oldflog + newflog;



        double tlog = transProbLog10(s1pos, s2pos);
        if (print) {
            System.err.println("TRANS: " + s1pos + "\t" + s2pos + "\t" + s1.get(s1pos) + "\t" + s2.get(s2pos) + "    = " + tlog + "  " + Math.pow(10, tlog));
        }
        DistortionKey k = new DistortionKey(s1pos, s2pos, l, m);
        double dlog = dist.getLog10(k);
        if (print) {
            System.err.println("Dist-key " + k + "  " + s1pos + "," + s2pos + "," + l + "," + m + "\t\t" + dlog + "  " + Math.pow(10, tlog));
        }
        tryPtprodlog = Ptprodlog + tlog + dlog;
        if (print) {
            System.err.println("\nDELTA    oldPt " + Ptprodlog + "\n   k" + k + "\n" + s2pos + " t " + Math.pow(10, tlog) + " d  " + Math.pow(10, dlog) + "\n" + "tlog " + tlog + "   dlog " + dlog + "\n");
            System.err.println("NEW: " + tryPtprodlog);
        }



        double tryresLog = tryPchooselog + tryPp0log + tryPp1log + tryPsprodlog + tryPtprodlog;
        if (print) {
            System.err.println(tryresLog + " = " + tryPchooselog + " + " + tryPp0log + " + " + tryPp1log + " + " + tryPsprodlog + " + " + tryPtprodlog);

            System.err.println(Math.pow(10, tryresLog) + " = " + Math.pow(10, tryPchooselog) + " * " + Math.pow(10, tryPp0log) + " * " + Math.pow(10, tryPp1log) +
                    " * " + Math.pow(10, tryPsprodlog) + " * " + Math.pow(10, tryPtprodlog));

        }
        return new alignRes(s1pos, s2pos, tryPchooselog, tryPp0log, tryPp1log, tryPsprodlog, tryPtprodlog, tryresLog);
    }


    private double transProbLog10(int s, int t) {

        if (s1.get(s) == 1) {
//            System.err.println("------ UNKNOWN -----------");
//            System.err.println("S1WORD " + ss1.get(s - 1));
//            System.err.println("S2WORD " + ss2.get(t - 1));
//            
            String[] tok = ss1.get(s - 1).split("/");
            String sw = tok[0];
            tok = ss2.get(t - 1).split("/");
            String tw = tok[0];
            //  System.err.println(sw +  "  " + tw);
            int maxL = Math.max(sw.length(), tw.length());
            String lcs = AlignmentHelper.LCS.LCSAlgorithm(sw, tw);
            double lcsr = (double) lcs.length() / maxL;
            //   System.err.println(sw + " " + tw  + "  : " + lcs + "     "+ lcsr);
            double lcsrres = lcsr / 10;
            return Math.log10(lcsrres);

        }

        return trans.getLog10(s1.get(s), s2.get(t));
    }


    private void update() {


        // Find fertilities
        List<Integer> s1fert = new ArrayList<Integer>(s1.size());


        for (int i = 0; i < s1.size(); i++) {

            s1fert.add(a.get(i).size());
        }

        int phi0 = s1fert.get(0);
        int mphi0;

        mphi0 = m - phi0;

        double Pchoose = choose(mphi0, phi0);
        if (Pchoose <= 1) {
            Pchoose = 1;
        }
        Pchooselog = Math.log10(Pchoose);

        int m2phi0 = (m - 2 * phi0);
        if (m2phi0 < 0) {
            m2phi0 = 1;
        }

        Pp0log = m2phi0 * p0log;

        Pp1log = phi0 * p1log;

        Psprodlog = 0;
        for (int i = 1; i < l; i++) {
            double factlog = Math.log10(fact(s1fert.get(i)));
            double flog = fert.getLog10(s1.get(i), s1fert.get(i));
            double temp = factlog + flog;
            Psprodlog = Psprodlog + temp;
        }


        Map<Integer, Integer> aj = new HashMap<Integer, Integer>();

        for (Integer gwa : a.keySet()) {
            for (Integer t : a.get(gwa)) {
                if (aj.containsKey(t)) {
                    System.err.println("Update: WARNING. Word " + t + "is multiple aligned");
                }
                aj.put(t, gwa);
            }
        }

        Ptprodlog = 0;


        for (int j = 1; j < m; j++) {
            // Is this translation the right way or should it be reversed?????
            // What is a translation og what in TranslationTableMem
            if (aj.containsKey(j) && aj.get(j) != 0) {


                double tlog = transProbLog10(aj.get(j), j);//trans.getLog10()), ));

                DistortionKey k = new DistortionKey(aj.get(j), j, l, m);

                double dlog = dist.getLog10(k);

//System.err.println("\n"+ k + "\n" + j + " t " + Math.pow(10, tlog) + " d  " + Math.pow(10, dlog) + "\n" + "tlog " + tlog + "   dlog " + dlog+"\n");

                double temp = tlog + dlog;
                Ptprodlog = Ptprodlog + temp;
            }
        }

        res = Pchooselog + Pp0log + Pp1log + Psprodlog + Ptprodlog;

//        System.err.println(res + " = " + Pchooselog + " + " + Pp0log + " + " + Pp1log + " + " + Psprodlog + " + " + Ptprodlog);
//
//        System.err.println(Math.pow(10, res) + " = " + Math.pow(10, Pchooselog) + " * " + Math.pow(10, Pp0log) + " * " + Math.pow(10, Pp1log) +
//                " * " + Math.pow(10, Psprodlog) + " * " + Math.pow(10, Ptprodlog));


    }


    public SentenceAlignement getSA() {

        List<WordAlignement> lwa = new ArrayList<WordAlignement>();
        for (Integer sa1 : a.keySet()) {
            if (sa1 != 0 && !a.get(sa1).isEmpty()) {
                Set<Integer> ssa1 = new HashSet<Integer>();
                ssa1.add(sa1 - 1);
                Set<Integer> sa2 = new HashSet<Integer>();
                for (Integer ga : a.get(sa1)) {
                    sa2.add(ga - 1);
//                    System.err.println("AAA " + sa1 + "  " + sa2);
                }
                lwa.add(new WordAlignement(ssa1, sa2));
            }
        }

        return new SentenceAlignement(ss1, ss2, lwa);
    }

    // Compute n choose k, which is the number of ways you can choose k objects
    // out of a set of n.  It is equal to n! / (k! * (n-k)!) .
    private static long chooseslow(int n, int k) {
        return fact(n) / (fact(k) * fact(n - k));
    }


    private static long choose(int n, int k) {


        if (k > n) {
            return 0;
        }

        if (k > n / 2) {
            k = n - k;
        } // faster

        double accum = 1;
        for (int i = 1; i <= k; i++) {
            accum = accum * (n - k + i) / i;
        }

        return (long) (accum + 0.5); // avoid rounding error

    }

    // Compute n!.
    private static long fact(int n) {
        long answer = 1;    // Accumulate the product

        for (long i = 1; i <= n; i++) {
            answer *= i;
        }

        return answer;
    }


    @Override
    public String toString() {

        return s1 + "\n" + s2 + "\n" + a;
    }
}
